/****************************************************************************
 * Copyright (c) 2010 Torkild U. Resheim
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Torkild U. Resheim  - initial API and implementation
 *****************************************************************************/
package no.resheim.buildmonitor.core.data;

import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Observable;

import no.resheim.buildmonitor.core.data.Job.Health;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;

/**
 * Basic build server representation. This must be sub-classed to suit the needs
 * of the particular build servers.
 * <p>
 * The {@link URI} serves as the unique identifier of the server.
 * </p>
 * 
 * 
 * @author Torkild Ulvøy Resheim
 * @since 1.0
 */
public abstract class Server extends Observable implements IChangeReporter {

	private static final String EMPTY_STRING = "";

	/** The description of the server */
	private String description = EMPTY_STRING;

	/** A list of jobs that the server maintains */
	private final HashMap<URI, Job> jobs;

	/** The name of the server instance */
	private String name = EMPTY_STRING;

	/** The status of the server */
	private IStatus status = Status.OK_STATUS;

	/** The URI of the server */
	private final URI uri;

	/** The update frequency of the server */
	private int pollInterval = 10000;

	/**
	 * Note that the <i>name</i> will be used in user interfaces.
	 * 
	 * @param name
	 *            the name of the server
	 * @param uri
	 *            the URI of the server
	 */
	protected Server(String name, URI uri) {
		super();
		this.name = name;
		this.uri = uri;
		if (uri == null) {
			throw new IllegalArgumentException("Build URI cannot be NULL.");
		}
		if (name == null) {
			throw new IllegalArgumentException("Build name cannot be NULL.");
		}
		jobs = new HashMap<URI, Job>();
	}

	/**
	 * Adds a new job to the list of jobs. Note that the identifier will be used
	 * to test if the job is already present. If not, this instance will be
	 * marked as changed and observers will be notified.
	 * 
	 * @param job
	 *            the job to add
	 */
	public void addJob(Job job) {
		synchronized (jobs) {
			if (!jobs.containsKey(job.getUri())) {
				jobs.put(job.getUri(), job);
				setChanged();
				notifyObservers();
			}
		}
	}

	private String changeDescription;

	/**
	 * Returns a description or summary of the last change that occurred in the
	 * server.
	 * 
	 * @return a description of the change
	 */
	@Override
	public String getChangeDescription() {
		return changeDescription;
	}

	/**
	 * Sets the description of the last change.
	 * 
	 * @param changeDescription
	 *            a short description
	 */
	@Override
	public void setChangeDescription(String changeDescription) {
		this.changeDescription = changeDescription;
	}

	/**
	 * Removes the job from the list of jobs. Note that the identifier will be
	 * used to test if the job is already present. If is and can be removed,
	 * this instance will be marked as changed and observers will be notified.
	 * 
	 * @param job
	 *            the job to remove
	 */
	public void removeJob(Job job) {
		synchronized (jobs) {
			if (jobs.containsKey(job.getUri())) {
				jobs.remove(job.getUri());
				setChanged();
				notifyObservers();
			}
		}
	}

	/**
	 * Connects the server instance to it's URI and obtain the list of jobs etc.
	 * 
	 * @return a status to log if something goes wrong
	 */
	public abstract IStatus connect();

	/**
	 * Allows the implementor to disconnect from the server. This method is
	 * normally only called when the instance is removed from the system.
	 * 
	 * @return a status to log if something goes wrong
	 */
	public abstract IStatus disconnect();

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (!(obj instanceof Server))
			return false;
		Server other = (Server) obj;
		if (!uri.equals(other.uri))
			return false;
		return true;
	}

	/**
	 * Returns the description of the server.
	 * 
	 * @return the server description
	 */
	public String getDescription() {
		return description;
	}

	/**
	 * Returns the job of the specific ID.
	 * 
	 * @param id
	 *            the job identifier
	 * @return the job or <code>null</code> if not found
	 */
	public Job getJob(URI id) {
		return jobs.get(id);
	}

	@Override
	public String toString() {
		return getName();
	}

	/**
	 * Returns the list of jobs that the build server maintains.
	 * 
	 * @return the list of jobs
	 */
	public Collection<Job> getJobs() {
		return jobs.values();
	}

	/**
	 * While Buildmonitor will keep a tab on all jobs it can be convenient that
	 * the UI only gets to know about jobs that matches certain criteria.
	 * 
	 * @param enabled
	 *            only return enabled jobs
	 * @return
	 */
	public Collection<Job> getFilteredJobs(boolean enabled) {
		ArrayList<Job> filteredJobs = new ArrayList<Job>();
		for (Job job : jobs.values()) {
			// ignore disabled jobs
			if (enabled && job.getHealth().equals(Health.DISABLED)) {
				continue;
			}
			filteredJobs.add(job);
		}
		return filteredJobs;
	}

	/**
	 * Returns the name of the build server.
	 * 
	 * @return the server name
	 */
	public String getName() {
		return name;
	}

	/**
	 * Returns the status of the server
	 * 
	 * @return the server status
	 */
	public IStatus getStatus() {
		return status;
	}

	/**
	 * Returns the URI of the server.
	 * 
	 * @return the server URI.
	 */
	public URI getUri() {
		return uri;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + uri.hashCode();
		return result;
	}

	/**
	 * Tests to see if the server already keeps track of the job with the
	 * specified identifier.
	 * 
	 * @param id
	 *            the server identifier
	 * @return <code>true</code> if the job is kept track of
	 */
	public boolean hasJob(URI id) {
		return jobs.containsKey(id);
	}

	/**
	 * Sets the description of the server. A <code>null</code> value is not
	 * allowed.
	 * 
	 * @param description
	 *            the server description
	 */
	public void setDescription(String description) {
		if (!this.description.equals(description)) {
			this.description = description;
			setChanged();
		}
	}

	/**
	 * Sets the name of the server. A <code>null</code> value is not allowed.
	 * 
	 * @param name
	 *            the server name
	 */
	public void setName(String name) {
		if (!this.name.equals(name)) {
			this.name = name;
			setChanged();
		}
	}

	/**
	 * Sets the status of the server. A <code>null</code> value is not allowed.
	 * 
	 * @param status
	 *            the new status
	 */
	public void setStatus(IStatus status) {
		if (!this.status.equals(status)) {
			this.status = status;
			setChanged();
		}
	}

	/**
	 * Returns the update frequency of the server.
	 * 
	 * @return the update frequency
	 */
	public int getPollInterval() {
		return pollInterval;
	}

	/**
	 * Sets the update frequency of the server in the minimum number of
	 * milliseconds that must pass before each poll.
	 * 
	 * @param pollInterval
	 *            the update frequency
	 */
	public void setPollInterval(int pollInterval) {
		this.pollInterval = pollInterval;
	}

}
